#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import clocks
import comm_channels
import cPickle
import os
import services
import types

class protocol(object):
    def __init__(self, clock, filesystem):
        self.clock_ = clock
        self.callback_ = lambda: None
        self.ck_counter_ = -1
        self.BASIC_ = 0
        self.FORCED_ = 1
        self.fs_ = filesystem    
        
    def print_output(self, text):
        self.clock_.print_output(text)

    def close_channel(self, dest):
        self.clock_.close_channel(dest)

    def send_message(self, dest, msg):
        self.clock_.send_message(dest, msg)
        
    def recv_message(self):
        return self.clock_.recv_message()

    def network_size(self):
        return self.clock_.network_size()

    def increase_clock(self):
        self.clock_.increase_clock()

    def get_pid(self):
        return self.clock_.get_pid()

    def get_hosts(self):
        return self.clock_.get_hosts()
    
    def channel_state(self, channel):
        return self.clock_.channel_state(channel)

    def get_clock(self):
        return self.clock_.get_clock()

    def get_checkpoint_counter(self):
        return self.ck_counter_

    def set_callback(self, function):
        if type(function) in [types.FunctionType, types.MethodType]:
            self.callback_ = function
        else:
            raise(TypeError, 'Callback is not a function or method')

    def control_information_(self):
        return None

    def take_basic_checkpoint(self):
        return self.take_checkpoint_(self.BASIC_)

    def take_forced_checkpoint_(self):
        return self.take_checkpoint_(self.FORCED_)

    def take_checkpoint_(self, ckpt_type):
        self.ck_counter_ += 1
        self.clock_.increase_clock()
        return (
            (self.get_pid(),
             self.ck_counter_,
             ckpt_type,
             self.get_clock()),
            self.callback_(),
            self.control_information_(),
        )

    def store_checkpoint_(self, state):
        f = self.fs_.OpenFile("%04d-%08d.ckpt" % (
                state[0][0], state[0][1]), "w")
        cPickle.dump(state, f)
        f.close()

    def remove_checkpoint_(self, proc, id):
        self.fs_.DeleteFile("%04d-%08d.ckpt" % (proc, id))

    def close(self):
        self.clock_.close()

class BCS(protocol):
    def __init__(self, clock, filesystem):
        self.lc_ = 0
        protocol.__init__(self, clock, filesystem)

    def send_message(self, dest, msg):
        self.lc_ += 1
        self.clock_.send_message(dest, (msg, self.lc_))

    def recv_message(self):
        self.lc_ += 1
        origin, content = self.clock_.recv_message()
        msg, m_lc = content
        if self.lc_ < m_lc:
            self.lc_ = m_lc
            self.store_checkpoint_(self.take_forced_checkpoint_())
        return (origin, msg)

    def take_basic_checkpoint(self):
        self.lc_ += 1
        self.store_checkpoint_(protocol.take_basic_checkpoint(self))

    def control_information_(self):
        return self.lc_

class FDAS(protocol):
    def __init__(self, clock, filesystem):
        protocol.__init__(self, clock, filesystem)
        self.dv_ = {}
        for i in self.get_hosts():
            self.dv_[i] = 0
        self.dv_[self.get_pid()] += 1
        self.sent_ = False

    def send_message(self, dest, msg):
        self.sent_ = True
        self.clock_.send_message(dest, (msg, self.dv_))

    def recv_message(self):
        origin, content = self.clock_.recv_message()
        msg, m_dv = content
        if self.sent_ and m_dv[origin] > self.dv_[origin]:
            self.sent_ = False
            self.store_checkpoint_(self.take_forced_checkpoint_())
            self.dv_[self.get_pid()] += 1
        for i in self.get_hosts():
            self.dv_[i] = max(self.dv_[i], m_dv[i])
        return (origin, msg)

    def take_basic_checkpoint(self):
        self.sent = False
        self.store_checkpoint_(protocol.take_basic_checkpoint(self))
        self.dv_[self.get_pid()] += 1

    def control_information_(self):
        return self.dv_


class RDT_LGC_FDAS(FDAS):
    
    INDEX_ = 0
    COUNTER_ = 1
    def __init__(self, clock, filesystem):
        FDAS.__init__(self, clock, filesystem)
        self.cm_ = {}
        for i in self.get_hosts():
            self.cm_[i] = None

    def new_ckpt_(self, i, ind):
        self.cm_[i] = [ind, 1]

    def free_(self, i):
        if self.cm_[i] != None:
            self.cm_[i][self.COUNTER_] -= 1
            if self.cm_[i][self.COUNTER_] == 0:
                self.remove_checkpoint_(self.get_pid(),
                                        self.cm_[i][self.INDEX_])
        self.cm_[i] = None

    def link_(self, i, j):
        self.cm_[i] = self.cm_[j]
        self.cm_[i][self.COUNTER_] += 1


    def recv_message(self):
        origin, content = self.clock_.recv_message()
        msg, m_dv = content

        if m_dv[origin] > self.dv_[origin] and self.sent_:
            self.sent_ = False
            self.store_checkpoint_(self.take_forced_checkpoint_())
            self.free_(self.get_pid())
            self.new_ckpt_(self.get_pid(), self.ck_counter_)
            self.dv_[self.get_pid()] += 1

        for i in self.get_hosts():
            if m_dv[i] > self.dv_[i]:
                self.dv_[i] = m_dv[i]
                self.free_(i)
                self.link_(i, self.get_pid())
            
        return (origin, msg)

    def take_basic_checkpoint(self):
        self.sent_ = False
        self.store_checkpoint_(protocol.take_basic_checkpoint(self))
        self.free_(self.get_pid())
        self.new_ckpt_(self.get_pid(), self.ck_counter_)
        self.dv_[self.get_pid()] += 1


class RDT_DISKLESS_FDAS(RDT_LGC_FDAS):
    
    # ckpt fields
    DVK_ = 2
    # message types
    MESSAGE_ = 0
    STORE_ = 1
    CONFIRM_ = 2
    ERASE_ = 3

    def __init__(self, clock, filesystem, lambdaalgorithm, ring):
        RDT_LGC_FDAS.__init__(self, clock, filesystem)
        
        self.alg_ = lambdaalgorithm
        self.dvk_ = {}
        self.log = False
        for i in self.get_hosts():
            self.dvk_[i] = -1
        self.max_ = {}
        self.ring_ = ring
        self.ring_.set_comm(self.clock_)
        self.lc_ = -1
        self.des_ = []
        self.max_des_ = 0

    def new_ckpt_(self, i, ind):
        self.cm_[i] = [ind, 1, None]

    def free_(self, i):
        if self.cm_[i] != None:
            self.cm_[i][self.COUNTER_] -= 1
            if self.cm_[i][self.COUNTER_] == 0:
                self.cm_[i][self.DVK_] = self.dv_.copy()
                self.des_.append(self.cm_[i])
        self.cm_[i] = None

    def package_unecessary_(self, ckpt):
        x = True
        for i in self.get_hosts():
            if ckpt[self.DVK_][i] > self.dvk_[i]:
                x = False
        return x

    def collect_(self):

        if self.log and len(self.des_) > self.max_des_:
            self.max_des_ = len(self.des_)
            self.print_output("DES: %d em %d" % (self.max_des_,
                                                 self.ck_counter_))
            
        erase = [i for i in self.des_ 
                 if self.package_unecessary_(i)]
        for ckpt in erase:
            self.remove_package_(self.get_pid(), ckpt[self.INDEX_])
            for i in xrange(0, self.alg_.packages() - 1):
                self.ring_.send_pos(i, [self.ERASE_, 
                                        ckpt[self.INDEX_]])
        self.des_ = [i for i in self.des_ 
                     if not self.package_unecessary_(i)]

    def send_message(self, dest, msg):
        self.sent_ = True
        self.clock_.send_message(dest, [0, (msg, self.dv_, self.dvk_)])

    def propagate_(self, ck):
        packages = self.alg_.Disperse(ck)
        self.store_checkpoint_(ck)
        self.store_package_(self.clock_.get_pid(), 
                            self.ck_counter_,
                            packages.pop())
        self.max_[self.get_pid()] = self.ck_counter_
        for i in xrange(0, len(packages)):
            self.ring_.send_pos(
                i, [self.STORE_, 
                    self.ck_counter_,
                    packages.pop()])

    def recv_message(self):
        content = (-1,) # enter the loop at least once.
        origin = None
        msg = None
        while content[0] != self.MESSAGE_:
            origin, content = self.clock_.recv_message()
            if content[0] == self.MESSAGE_:
                msg, m_dv, m_dvk = content[1]

                if self.sent_ and m_dv[origin] > self.dv_[origin]:
                    self.sent_ = False
                    ck = self.take_forced_checkpoint_()
                    self.propagate_(ck)
                    self.free_(self.get_pid())
                    self.new_ckpt_(self.get_pid(), self.ck_counter_)
                    self.dv_[self.get_pid()] += 1

                for i in self.get_hosts():
                    self.dvk_[i] = max(self.dvk_[i], m_dvk[i])
                    if self.dv_[i] < m_dv[i]:
                        self.dv_[i] = m_dv[i]
                        self.free_(i)
                        self.link_(i, self.get_pid())                        
                self.collect_()
            elif content[0] == self.STORE_:
                #Please note that it is a FIFO channel!!!
                ck, data = content[1:]
                self.store_package_(origin, ck, data)
                self.clock_.send_message(origin, [2, ck])
            elif content[0] == self.CONFIRM_: 
                self.max_[origin] = max(self.max_.get('origin'), content[1])
                if len(self.max_) == self.alg_.packages():
                    self.lc_ = max(self.lc_, min(self.max_.values()))
                    for i in xrange(self.dvk_[self.get_pid()], self.lc_):
                        self.remove_checkpoint_(self.get_pid(), i + 1)
                    self.dvk_[self.get_pid()] = self.lc_
            elif content[0] == self.ERASE_:
                self.remove_package_(origin, content[1])            
        return (origin, msg)

    def store_package_(self, proc, ckpt_id, data):
        f = self.fs_.OpenFile("%04d-%08d.package" % (
                proc, ckpt_id), "w")
        f.write(data)
        f.close()

    def remove_package_(self, proc, ckpt_id):
        self.fs_.DeleteFile("%04d-%08d.package" % (proc, ckpt_id))

    def take_basic_checkpoint(self):
        self.sent = False
        ck = protocol.take_basic_checkpoint(self)
        self.store_checkpoint_(ck)
        self.propagate_(ck)
        self.free_(self.get_pid())
        self.new_ckpt_(self.get_pid(), self.ck_counter_)
        self.collect_()
        self.dv_[self.get_pid()] += 1

def CkptProtocolsAF(name, clock, filesystem,
                    lambdaalgorithm=None, ring=None):
    
    if not clock:
        raise(ValueError, 'clock cannot be None')
    if not filesystem:
        raise(ValueError, 'filesystem cannot be None')
    if name in ('none'):
        return protocol(clock, filesystem)
    if name in ('BCS', 'FDAS', 'RDT_LGC_FDAS'):
        return globals()[name](clock, filesystem)
    elif name in ('RDT_DISKLESS_FDAS'):
        if not lambdaalgorithm:
            raise(ValueError, 'lambdaalgorithm cannot be None')
        if not ring:
            raise(ValueError, 'ring cannot be None')
        return globals()[name](clock, filesystem, lambdaalgorithm, ring)
    else:
        raise(ValueError, 'Inexistent Checkpoint Protocol')
                

